var Metaweb = new Array();

Metaweb.STATE_START = Components.interfaces.nsIWebProgressListener.STATE_START;
Metaweb.STATE_STOP = Components.interfaces.nsIWebProgressListener.STATE_STOP;

Metaweb.Utility = {
  bind : function(scope, fn, args) {
    if (!args) {
      args = new Array();
    }
    return function() {
      fn.apply(scope, args);
    };
  },

  getEndOfURL : function(url) {
    var temp1 = url.indexOf("#");
    var temp2 = url.indexOf("?");
    
    if (temp1 == -1) {
      return temp2;
    } else if (temp2 == -1) {
      return temp1;
    } else {
      return Math.min(temp1, temp2);
    }
  },

  getBareURL : function(url) {
    url = String(url);
    var endOfURL = Metaweb.Utility.getEndOfURL(url);
    if (endOfURL > 0) {
      url = url.substring(0, endOfURL);
    }
    return url;
  },

  getURLParameter : function(url) {
    url = String(url);
    var endOfURL = Metaweb.Utility.getEndOfURL(url);
    if (endOfURL > 0) {
      return url.substring(endOfURL);
    }
    return null;
  },

  trim : function(str) {
    return str.replace(/^\s*(\S*(\s+\S+)*)\s*$/, "$1");
  },
  
  xmlencode : function(str) {
    return str.replace(/\&/g,'&'+'amp;').replace(/</g,'&'+'lt;')
        .replace(/>/g,'&'+'gt;').replace(/\'/g,'&'+'apos;')
        .replace(/\"/g,'&'+'quot;');
  }
};

/** Controller for Metaweb
  */
Metaweb.Controller = function() {
  this.view = new Metaweb.View(this);
  this.model = new Metaweb.Model(this);
};

Metaweb.Controller.prototype = {
  initialize : function() {
    this.model.initialize();
  },

  stop : function() {
    this.model.stop();
  },

  onRegisterUserError : function(error_message) {
    this.view.toolbar.registerBox.onError(error_message);
  },
  
  onRegisterUserSuccess : function() {
    this.view.toolbar.onRegisterSuccess();
  },

  onLoginError : function(error_message) {
    this.view.toolbar.loginBox.onError(error_message);
  },

  onLoginSuccess : function(user_name) {
    this.view.toolbar.onLoginSuccess(user_name);
    if (this.view.sidebar)
      this.view.sidebar.onLoginSuccess();
    this.model.rating.getPageRating();
    this.model.tag.getPageTags();
    this.model.tag.getRecommendedPageTags();
    this.model.contactList.getContactList();
    this.model.reloadComments();
    this.model.link.getLinks();
    this.model.openedTabList.showGuidedBrowsing(
        this.model.openedTabList.currentTab);
  },

  onLogoutSuccess : function() {
    this.view.toolbar.onLogoutSuccess();
    if (this.view.sidebar)
      this.view.sidebar.onLogoutSuccess();
    this.model.tag.getPageTags();
    this.model.reloadComments();
    this.model.link.getLinks();
	//this.model.documentHandler.insertPageRatings(false);
    this.model.documentHandler.insertPageRatingsBulk(false);
  },

  showComments : function(xml_comments) {
    if (this.view.sidebar) {
      this.view.sidebar.commentTree.showComments(xml_comments);
    }
  },

  onChatMessageReceived : function(sender, message) {
    if (this.view.sidebar) {
      this.view.sidebar.chatBox.onMessageReceived(sender, message);
    }
  },

  onLeaveChatRoom : function() {
    if (this.view.sidebar) {
      this.view.sidebar.chatBox.clearContactList();
      this.view.sidebar.chatBox.clearChatTextbox();
    }
  },

  onOtherUserOnline : function(user_name) {
    if (this.view.sidebar) {
      this.view.sidebar.chatBox.onUserOnline(user_name);
    }
  },

  onOtherUserOffline : function(user_name) {
    if (this.view.sidebar) {
      this.view.sidebar.chatBox.onUserOffline(user_name);
    }
  },

  onChangeDisplayedChatRoom : function(participant, chat) {
    if (this.view.sidebar) {
      this.view.sidebar.chatBox.clearContactList();
      this.view.sidebar.chatBox.clearChatTextbox();
      if (participant != null && chat != null) {
        for (var jid in participant) {
          this.view.sidebar.chatBox.onUserOnline(participant[jid]);
        }
        for (var i = 0; i < chat.length; i++) {
          this.view.sidebar.chatBox.onMessageReceived(
              chat[i].from, chat[i].content);
        }
      }
    }
  },
  
  onFollowRequestReceived : function(requester, requester_nick, 
                                     requester_tabid, url) {
    this.view.toolbar.onFollowRequestReceived(
        requester, requester_nick, requester_tabid, url);
  },

  onFollowSuccess : function(tabid) {
    if (tabid == this.model.openedTabList.currentTab)
      this.view.toolbar.onFollowSuccess();
  },

  changeFollowing : function(followed) {
    if (this.model.connection.isLoggedin)
      this.view.toolbar.changeFollowing(followed);
  },
  
  onUpdatePageRatingSuccess : function(userRating, averageRating) {
    this.view.toolbar.onUpdatePageRatingSuccess(userRating, averageRating);
  },

  showTags : function(xmltags) {
    if (this.view.sidebar) {
      this.view.sidebar.tag.showTags(xmltags);
    }
  },

  showContactList : function(xmlcontacts) {
    if (this.view.sidebar) {
      this.view.sidebar.contactList.showContactList(xmlcontacts);
    }
  },
  
  onPageLoad : function(url, title) {
    this.view.toolbar.history.addHistory(url, title);
  },

  showLinks : function(xmllinks) {
    if (this.view.sidebar) {
      this.view.sidebar.links.showLinks(xmllinks);
    }
  },

  showTagsTextbox : function(xmltags) {
    this.view.toolbar.showTagsTextbox(xmltags);
    if (this.view.sidebar) {
      this.view.sidebar.showTagsTextbox(xmltags);
    }
  },
  
  showGuidedBrowsing : function(followed, follower) {
    //alert("follower:" + follower.length);
    if (this.view.sidebar) {
      this.view.sidebar.guidedBrowsing.showGuidedBrowsing(followed, follower);
    }
  },

  onStopGuidingSignalReceived : function() {
    this.view.toolbar.onUnfollow();
    this.view.toolbar.onStopGuidingSignalReceived();
  },
  
  showPagesWithTag : function(xml) {
    if (this.view.sidebar) {
      this.view.sidebar.tag.showPagesWithTag(xml);
    }
  },

  showLinkComment: function(xml, url, url_param, to) {
    if (this.view.sidebar) {
      this.view.sidebar.links.showLinkComment(xml, url, url_param, to);
    }
  },
  
  insertPageRatings : function(value) {
    this.model.openedTabList.setSettingsInsertRatings(value);
    //this.model.documentHandler.insertPageRatings(value);
    this.model.documentHandler.insertPageRatingsBulk(value);
  },
  
  onTabSelected : function() {
    var tabSettingInsertRatingsValue = 
        this.model.openedTabList.tabList[this.model.openedTabList.currentTab].settingsInsertRatings;
    this.view.toolbar.settings.setInsertRatingsCheck(tabSettingInsertRatingsValue);
  },

  onInviteUserSuccess : function(xmlresult) {
    if (this.view.toolbar.inviteUserBox) {
      this.view.toolbar.inviteUserBox.onSuccess(xmlresult);
    }
  }
};

Metaweb.View = function(controller) {
  this.controller = controller;
  this.toolbar = null;
  this.sidebar = null;
}

/** Model
  */
Metaweb.Model = function(controller) {
  this.controller = controller;

  this.comment = new Metaweb.Model.Comment(this.controller);
  this.connection = new Metaweb.Model.Connection(this.controller);

  this.currentURL = null;
  this.currentURLParameter = null;
  this.changeListener = null;
  this.openedTabList = new Metaweb.Model.TabList(this.controller);
  this.chatRoomList = new Metaweb.Model.ChatList(this.controller);
  this.rating = new Metaweb.Model.Rating(this.controller);
  this.tag = new Metaweb.Model.Tag(this.controller);
  this.contactList = new Metaweb.Model.ContactList(this.controller);
  var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
      .getService(Components.interfaces.nsIWindowMediator);
  this.mainWindow = wm.getMostRecentWindow("navigator:browser");
  this.link = new Metaweb.Model.Link(this.controller);
  this.documentHandler = new Metaweb.Model.DocumentHandler(this.controller);
  this.contextMenuHandler = new Metaweb.Model.ContextMenuHandler(this.controller); this.contextMenuHandler.init();
  this.autoCompleter = new Metaweb.Model.AutoCompleter(this.controller); this.autoCompleter.init();
  this.prefManager = Components.classes["@mozilla.org/preferences-service;1"]
      .getService(Components.interfaces.nsIPrefService)
      .getBranch("extensions.metaweb.");
  this.prefManager.QueryInterface(Components.interfaces.nsIPrefBranch2);

};

Metaweb.Model.prototype = {
  initialize : function() {
    this.prefManager.addObserver("", this, false);
    this.openedTabList.initialize();
    this.processNewURL(this.mainWindow.gBrowser.webNavigation.currentURI);
  },
  
  stop : function() {
    this.prefManager.removeObserver("", this);
    this.connection.logout();
    this.mainWindow.gBrowser.removeProgressListener(this.changeListener);
  },

  processNewURL : function(aURI) {
    var url = Metaweb.Utility.getBareURL(aURI.spec);
    var param = Metaweb.Utility.getURLParameter(aURI.spec);
    
    if (this.currentURL != url || this.currentURLParameter != param) {
      this.currentURLParameter = param;
      if (this.currentURL != url) {
        // now we know the url is new...
        this.currentURL = url;
        this.comment.getComments(this.currentURL, true);
      }
      this.rating.getPageRating();
      this.tag.getPageTags();
      this.tag.getRecommendedPageTags();
      this.contactList.getContactList();
      this.link.getLinks();
    }
  },

  loadPage : function(url) { 
    content.window.location.href = url; 
  },

  reloadComments : function() {
    this.comment.getComments(this.currentURL, false);
  },

  getCurrentChatRoom : function() {
    return this.chatRoomList.displayedChatRoom.chat;
  }
};

Metaweb.Model.TabList = function(controller) {
  this.controller = controller;
  this.tabList = new Object();
  this.nextId = 1;
  this.currentTab = null;
};

Metaweb.Model.TabList.prototype = {
  initialize : function() {
    var container = this.controller.model.mainWindow.gBrowser.tabContainer;
    for (var i = 0; i < container.itemCount; ++i) {
      var tab = container.getItemAtIndex(i);
      tab.setAttribute("tabid", this.nextId);
      var browser = this.controller.model.mainWindow.gBrowser
          .getBrowserForTab(tab);
      browser.setAttribute("tabid", this.nextId);
      this.tabList[tab.getAttribute("tabid")] = new Metaweb.Model.Tab(
          this.controller, this.nextId, browser,
          browser.contentDocument.location);
      this.nextId ++;
    }
    var temp = this;
    var tab_added_handler = function(event) {
      temp.onTabAdded.call(temp, event);
    };
    container.addEventListener("TabOpen", tab_added_handler, false);
    var tab_closed_handler = function(event) {
      temp.onTabClosed.call(temp, event);
    };
    container.addEventListener("TabClose", tab_closed_handler, false);
    var tab_selected_handler = function(event) {
      temp.onTabSelected.call(temp, event);
    };
    container.addEventListener("TabSelect", tab_selected_handler, false);
    this.currentTab = container.selectedItem.getAttribute("tabid");
    this.changeListener = new Metaweb.Model.TabsChangeListener(this.controller);
    this.controller.model.mainWindow.gBrowser
        .addTabsProgressListener(this.changeListener);
  },

  onTabAdded : function(event) {
    var tabid = String(this.nextId ++);
    event.target.setAttribute("tabid", tabid);
    var browser = this.controller.model.mainWindow.gBrowser
                  .getBrowserForTab(event.target);
    this.controller.model.mainWindow.gBrowser.getBrowserForTab(event.target)
        .setAttribute("tabid", tabid);
    this.tabList[tabid] = new Metaweb.Model.Tab(
        this.controller, tabid, browser, browser.contentDocument.location);
  },

  onTabClosed : function(event) {
    // alert('onTabClosed');
    this.controller.model.chatRoomList.leaveRoom(
        this.tabList[event.target.getAttribute("tabid")].url);
    delete this.tabList[event.target.getAttribute("tabid")];
  },

  onTabSelected : function(event) {
    var browser = this.controller.model.mainWindow.gBrowser
                  .getBrowserForTab(event.target); 
    var url = browser.contentDocument.location;
    var container = this.controller.model.mainWindow.gBrowser.tabContainer;
    var tabid = container.selectedItem.getAttribute("tabid");
    if (this.currentTab != tabid) {
      var ios = Components.classes["@mozilla.org/network/io-service;1"]
                .getService(Components.interfaces.nsIIOService);
      var uri = ios.newURI(url, null, null);
      this.controller.model.processNewURL(uri);
      this.currentTab = tabid;
      this.controller.changeFollowing(this.tabList[this.currentTab].followed);
      this.controller.onTabSelected();
    }
  },

  onLocationChange : function(browser, uri) {
    if (!browser.hasAttribute("tabid"))
      return;
    var tabid = browser.getAttribute("tabid");
    var url = Metaweb.Utility.getBareURL(uri.spec);
    if (this.currentTab == tabid) {
      this.controller.model.processNewURL(uri);
    }
    if (this.tabList[tabid].url != url) {
      this.controller.model.chatRoomList.leaveRoom(this.tabList[tabid].url);
    }
    this.tabList[tabid].updateURI(uri);
    if (this.controller.model.connection.xmppConnection) {
      this.tabList[tabid].onPageLoad(uri.spec);
    }
  },

  addFollower : function(name, url, tabid) {
    for (var tab in this.tabList) {
      if (String(this.tabList[tab].url) == String(url)) {
        this.tabList[tab].addFollower(String(name), String(tabid));
        return;
      }
    }

    // error, no such tab
  },

  sendFollowRequest : function(name) {
    this.controller.model.chatRoomList
        .sendFollowRequest(name, this.tabList[this.currentTab].url, 
                           this.currentTab);
  },

  sendFollowResponse : function(response, requester, requester_nick, 
                                requester_tabid, url) {
    if (response) {
      this.addFollower(requester, url, requester_tabid);
    }
    this.controller.model.chatRoomList.sendFollowResponse(
        response, requester_nick, requester_tabid, url);
  },

  onFollowResponseReceived : function(name, tabid, response) {
    if (response == "true" && tabid in this.tabList) {
      // display success message
      this.tabList[tabid].follow(name);
      this.controller.onFollowSuccess(tabid);
    } else {
      // display error message
    }
  },

  onFollowRequestReceived : function(requester, requester_nick,
                                     requester_tabid, url) {
    this.controller.onFollowRequestReceived(requester, requester_nick,
                                            requester_tabid, url);
  },

  sendUnfollowRequest : function() {
    this.tabList[this.currentTab].sendUnfollowRequest();
  },

  showGuidedBrowsing : function(tabid) {
    if (this.currentTab == tabid) {
      this.controller.showGuidedBrowsing(
          this.tabList[this.currentTab].followed,
          this.tabList[this.currentTab].follower);
    }
  },

  sendStopGuidingSignal : function(follower_name) {
    this.tabList[this.currentTab].sendStopGuidingSignal(follower_name);
  },
  
  setSettingsInsertRatings : function(value) {
    this.tabList[this.currentTab].settingsInsertRatings = value;
  },
  
  afterPageLoad : function(browser) {
    if (!browser.hasAttribute("tabid"))
      return;
    var tabid = browser.getAttribute("tabid");
    this.tabList[tabid].updateTitle();
    if (this.currentTab == tabid) {
      this.controller.model.documentHandler.afterPageLoad();
    }
  }
};

Metaweb.Model.Tab = function(controller, tabid, browser, uri) {
  this.controller = controller;
  this.tabid = tabid;
  this.browser = browser;
  this.url = Metaweb.Utility.getBareURL(uri);
  this.url_param = Metaweb.Utility.getURLParameter(uri);
  this.history = [this.url];
  this.follower = new Array();
  this.followed = null;
  this.channel = null;
  this.settingsInsertRatings = false;
};

Metaweb.Model.Tab.prototype = {
  updateURI: function(uri) {
    this.url = Metaweb.Utility.getBareURL(uri.spec);
    this.url_param = Metaweb.Utility.getURLParameter(uri.spec);
  },

  updateTitle : function() {
    var args = {url : this.url,
                url_param : this.url_param};
    this.controller.model.connection.doServerRequest("get_title", args,
        this.controller.model.connection.createServerRequestCallback(
            this, this.updateTitleCallback));
  },
 
  updateTitleCallback : function(serverRequest) {
    if (serverRequest.readyState == 4) {
      if (serverRequest.status == 200) {
        result = new XML(serverRequest.responseText);
        title = String(result.title);
        //alert(this.url + "\n" + title + "\n" + this.browser.contentTitle);
        if (title != this.browser.contentTitle) {
          var args = {url : this.url,
                      url_param : this.url_param,
                      title : this.browser.contentTitle};
          this.controller.model.connection.doServerRequest("update_title", args);
        }
      }
    }
  },

  follow : function(user) {
    if (this.channel == null) {
      this.channel = XMPP.createChannel();
    }
    user = String(user);
    this.addFollowListener(user);
    this.followed = user;
    this.controller.model.openedTabList.showGuidedBrowsing(this.tabid);
  },

  addFollower : function(user, tabid) {
    if (this.channel == null) {
      this.channel = XMPP.createChannel();
    }
    this.follower.push({user : user, tabid : tabid });
    this.controller.model.openedTabList.showGuidedBrowsing(this.tabid);
    this.addGuideListener(user, tabid);
  },

  loadPage : function(url) {
    if (this.browser.contentDocument.wrappedJSObject.location != url) {
      this.browser.contentDocument.wrappedJSObject.location = url;
    }
  },

  addToHistory : function(url) {
  },

  onPageLoad : function(url) {
    var jid = this.controller.model.connection.xmppConnection.account.jid;
    for(var i = 0; i < this.follower.length; i++) {
      XMPP.send(jid,
        <message from={jid}
                 to={this.follower[i].user}
                 type="chat">
          <body/>
          <follow type="change_page"
                  tabid={this.follower[i].tabid}
                  url={this.browser.contentDocument.wrappedJSObject.location}/>
        </message>);
    }
  },

  

  addGuideListener : function(user, tabid) {
    var temp = this;
    
    var unfollow_handler = function (message) {
      var follower_jid = String(message.stanza.@from);
      var follower_tabid = String(message.stanza.follow.@tabid);
      temp.onUnfollowRequestReceived.call(temp, follower_jid, follower_tabid);
    };
    
    this.unfollowListener = this.channel.on({
      event : 'message',
      direction : 'in',
      stanza : function(m) {
        return String(m.@from).toLowerCase() == String(user).toLowerCase() &&
               m.follow &&
               m.follow.@type == "unfollow" &&
               String(m.follow.@tabid) == temp.tabid;
      }},
      unfollow_handler);
    
  },
  
  addFollowListener : function(user) {
    var temp = this;

    var follow_handler = function (message) {
      temp.loadPage.call(temp, String(message.stanza.follow.@url)); 
    };


    //var unfollow_handler = function (message) {
    //  alert('unfollow_handler');
    //  var follower_jid = String(message.stanza.@from);
    //  var follower_tabid = String(message.stanza.follow.@tabid);
    //  temp.onUnfollowRequestReceived.call(temp, follower_jid, follower_tabid);
    //};

    
    this.followListener = this.channel.on({
      event : 'message',
      direction : 'in',
      stanza : function(m) {
        return String(m.@from).toLowerCase() == String(user).toLowerCase() &&
               m.follow &&
               m.follow.@type == "change_page" &&
               String(m.follow.@tabid) == temp.tabid;
      }},
      follow_handler);


    //this.unfollowListener = this.channel.on({
    //  event : 'message',
    //  direction : 'in',
    //  stanza : function(m) {
    //    return m.follow.@type == "unfollow";
    //  }},
    //  unfollow_handler);

      
    //this.unfollowListener = this.channel.on({
    //  event : 'message',
    //  direction : 'in',
    //  stanza : function(m) {
    //    return String(m.@from).toLowerCase() == String(user).toLowerCase() &&
    //           m.follow &&
    //           m.follow.@type == "unfollow" &&
    //           String(m.follow.@tabid) == temp.tabid;
    //  }},
    //  unfollow_handler);
      
    var stop_guiding_handler = function (message) {
      //alert('stop_guiding_handler');
      temp.onStopGuidingSignalReceived();
    };

      
    this.stopGuidingListener = this.channel.on({
      event : 'message',
      direction : 'in',
      stanza : function(m) {
        return m.follow &&
               m.follow.@type == "stop_guiding" &&
               String(m.follow.@tabid) == temp.tabid;
      }},
      stop_guiding_handler);
  },

  sendUnfollowRequest : function() {
    var jid = this.controller.model.connection.xmppConnection.account.jid;
    XMPP.send(jid,
      <message from={jid}
               to={this.followed}
               type="chat">
        <body/>
        <follow type="unfollow"
                tabid={this.tabid}/>
      </message>);
    this.followed = null;
    this.channel.forget(this.followListener);
    this.channel.forget(this.unfollowListener);
    this.channel.forget(this.stopGuidingListener);
    this.controller.model.openedTabList.showGuidedBrowsing(this.tabid);
  },

  onUnfollowRequestReceived : function(follower_jid, follower_tabid) {
    for (var i in this.follower) {
      if (this.follower[i].user == follower_jid && 
          this.follower[i].tabid == follower_tabid) {
        this.follower.splice(i, 1);
        this.channel.forget(this.unfollowListener);
        this.controller.model.openedTabList.showGuidedBrowsing(this.tabid);
        break;
      }
    }
  },

  sendStopGuidingSignal : function(follower_name) {
    var jid = this.controller.model.connection.xmppConnection.account.jid;
    var index = -1;
    for (var i in this.follower) {
      if (this.follower[i].user == follower_name) {
        index = i;
      }
    }
    if (index != -1) {
      this.follower.splice(i, 1);
      XMPP.send(jid,
         <message from={jid}
                  to={follower_name}
                  type="chat">
           <body/>
           <follow type="stop_guiding"
                   tabid={this.tabid}/>
         </message>);
      this.controller.model.openedTabList.showGuidedBrowsing(
        this.tabid, this.followed, this.follower);
    }
  },

  onStopGuidingSignalReceived : function() {
    this.followed = null;
    this.channel.forget(this.followListener);
    this.channel.forget(this.unfollowListener);
    this.channel.forget(this.stopGuidingListener);

    this.controller.model.openedTabList.showGuidedBrowsing(
        this.tabid, this.followed, this.follower);
    this.controller.onStopGuidingSignalReceived();
  }
};

Metaweb.Model.Connection = function(controller) {
  this.controller = controller;
  
  this.xmppConnection = null;
  this.isLoggedin = false;
};

Metaweb.Model.Connection.prototype = {
  createServerRequestCallback : function(context, callback, parameter) {
    return function(serverRequest) {
      if (parameter == null) {
        parameter = new Array();
      }
      parameter.splice(parameter.length, 0, serverRequest);
      return Metaweb.Utility.bind(context, callback, parameter);
    }
  },

  doServerRequest : function(command, args, callback) {
    var serverRequest = new XMLHttpRequest();
    var url = this.controller.model.prefManager.getCharPref("server");
    var params = "command=" + encodeURIComponent(command);;
    for ( var i in args) {
      params += "&" + i + "=" + encodeURIComponent(args[i]);
    }
    //alert(callback);
    if (callback != null) {
      serverRequest.onreadystatechange = callback(serverRequest);
      //alert( Metaweb.Utility.bind(this, callback, [serverRequest]));
    }
    serverRequest.open("POST", url, true);
    serverRequest.setRequestHeader("Content-type",
        "application/x-www-form-urlencoded");
    serverRequest.setRequestHeader("Content-length", params.length);
    serverRequest.setRequestHeader("Connection", "close");
    serverRequest.send(params);
  },

  registerNewUser : function (user_name, password, email) {
    var args = {user: user_name,
                password: password,
                email: email};
    this.doServerRequest("register_new_user", args,
        this.createServerRequestCallback(this, this.registerResult,
                                         [user_name, password]));
  },

  registerResult : function(user_name, password, serverRequest) {
    if (serverRequest.readyState == 4) {
      if (serverRequest.status == 200) {
        //alert(serverRequest.responseText);
        var xmlresult = new XML(serverRequest.responseText);
        if (xmlresult.returncode == "false") {
          this.controller.onRegisterUserError(xmlresult.errormessage);
        } else {
          this.controller.onRegisterUserSuccess();
          //this.login(user_name, password);
        }
      }
    }
  },

  login : function(user_name, password) {
    var args = new Array();
    args['user_name'] = user_name;
    args['password'] = password;
    this.doServerRequest("user_login", args, 
        this.createServerRequestCallback(this, this.loginResult));
    this.xmppConnection = new Metaweb.Model.Connection.XMPPConnection(
        user_name, password, this.controller);
    this.userName = user_name;
  },
  
  loginResult : function(serverRequest) {
    if (serverRequest.readyState == 4) {
      if (serverRequest.status == 200) {
        //alert(serverRequest.responseText);
        var xmlresult = new XML(serverRequest.responseText);
        if (xmlresult.returncode == "0") {
          this.controller.onLoginError(xmlresult.errormessage);
        } else {
          this.controller.onLoginSuccess(this.userName);
          this.isLoggedin = true;
          var temp = this.controller.model.chatRoomList;
          var xmpp_callback = Metaweb.Utility.bind(temp, temp.onLogin);
          this.xmppConnection.login(xmpp_callback);
        }
      }
    }
  },
  
  logout : function() {
    if (!this.isLoggedin)
      return;

    var serverRequest = new XMLHttpRequest();
    var url = this.controller.model.prefManager.getCharPref( "server");
    var params = "command=" + encodeURIComponent("user_logout");
    serverRequest.open("POST", url, false);
    serverRequest.setRequestHeader("Content-type",
        "application/x-www-form-urlencoded");
    serverRequest.setRequestHeader("Content-length", params.length);
    serverRequest.setRequestHeader("Connection", "close");
    serverRequest.send(params);
    this.isLoggedin = false;
    this.xmppConnection.logout();
    this.controller.onLogoutSuccess();
  },
 
  inviteUser : function(email) {
    var args = {email : email};
    this.doServerRequest("invite_user", args,
        this.createServerRequestCallback(this, this.inviteUserResult));
  },

  inviteUserResult : function(serverRequest) {
    if (serverRequest.readyState == 4) {
      if (serverRequest.status == 200) {
        var xmlresult = new XML(serverRequest.responseText);
        //alert(serverRequest.responseText);
        this.controller.onInviteUserSuccess(xmlresult);
      }
    }   
  },

  checkLoginStatus : function() {
    var args = new Array();
    this.doServerRequest("check_login_status", args,
        this.createServerRequestCallback(this, this.checkLoginStatusResult));
  },

  checkLoginStatusResult : function(serverRequest) {
    if (serverRequest.readyState == 4) {
      if (serverRequest.status == 200) {
        var xmlresult = new XML(serverRequest.responseText);
        // alert(xmlresult);
        if (xmlresult.loggedin == "1") {
          this.userName = xmlresult.username;
          this.controller.onLoginSuccess(this.userName);
        }
      }
    }
  }
};

Metaweb.Model.Connection.XMPPConnection = function(username, password,
                                                   controller) {
  this.controller = controller;
  this.username = username;
  var server = this.controller.model.prefManager.getCharPref("xmppserver");
  var domain = this.controller.model.prefManager.getCharPref("xmppdomain");
  var user_jid = username + '@' + domain + '/metaweb';
  this.account = {jid: user_jid, 
                  password: password,
                  connectionHost: server,
                  connectionPort: this.controller.model.prefManager
                      .getIntPref("xmppport"), 
                  connectionSecurity: 1};
};

Metaweb.Model.Connection.XMPPConnection.prototype = {
  login : function(callback) {
    XMPP.up(this.account, callback);
  },

  logout : function() {
    XMPP.down(this.account);
  },

  registerNewUser : function(username, password) {
    var domain = this.controller.model.prefManager.getCharPref("metawebdomain");
    var user_jid = username + '@' + domain + '/metaweb';
      XMPP.send('admin@' + domain + '/metaweb',
          <iq type='get' id='reg1'>
            <query xmlns='jabber:iq:register'/>
          </iq>);
  }
};

Metaweb.Model.ChatList = function(controller) {
  this.controller = controller;
  this.openedChatList = new Object();
  this.displayedChatRoom = {chat : null, url : null};
}

Metaweb.Model.ChatList.prototype = {
  joinRoom : function(url, room_id) {
    if (!(url in this.openedChatList)) {
      if (this.controller.model.connection.xmppConnection) {
        this.openedChatList[url] = 
            {chat : new Metaweb.Model.Chat(this, this.controller, room_id),
             count : 1, room_id : room_id};
        this.openedChatList[url].chat.joinRoom();
      } else {
        this.openedChatList[url] = {count : 1, room_id : room_id};
      }
    } else {
      this.openedChatList[url].count ++;
    }
  },

  leaveRoom : function(url) {
    // alert('leave room: ' + url);
    if (url in this.openedChatList) {
      this.openedChatList[url].count --;
      if (this.openedChatList[url].count == 0) {
        if (this.openedChatList[url].chat)
          this.openedChatList[url].chat.leaveRoom();
        delete this.openedChatList[url];
      }
    }
  },

  changeDisplayedChatRoom : function(url) {
    this.displayedChatRoom.url = url;
    if (this.openedChatList[url].chat) {
      this.displayedChatRoom.chat = this.openedChatList[url].chat;
      this.controller.onChangeDisplayedChatRoom(
          this.displayedChatRoom.chat.roomParticipant,
          this.displayedChatRoom.chat.chatContent);
    } else {
      this.displayedChatRoom.chat = null;
      this.controller.onChangeDisplayedChatRoom(null, null);
    }
  },

  onUserOnline : function(chatroom, participant) {
    if (chatroom == this.displayedChatRoom.chat) {
      this.controller.onOtherUserOnline(participant);
    }
  },

  onUserOffline : function(chatroom, participant) {
    if (chatroom == this.displayedChatRoom.chat) {
      this.controller.onOtherUserOffline(participant);
    }
  },
  
  onMessageReceived : function(chatroom, message) {
    if (chatroom == this.displayedChatRoom.chat) {
      this.controller.onChatMessageReceived(message.from, message.content);
    }
  },

  sendGroupChat : function(message) {
    this.displayedChatRoom.chat.sendGroupChat(message);
  },

  onLogin : function() {
    for (var url in this.openedChatList) {
      if (this.openedChatList[url].room_id) {
        this.openedChatList[url].chat = new Metaweb.Model.Chat(
            this, this.controller, this.openedChatList[url].room_id);
        this.openedChatList[url].chat.joinRoom();
      }
    }
    var url = this.controller.model.currentURL;
    this.changeDisplayedChatRoom(url);
  },

  sendFollowRequest : function(to, url, tabid) {
    this.displayedChatRoom.chat.sendFollowRequest(to, url, tabid);
  },
  
  sendFollowResponse : function(response, requester, requester_tabid, url) {
    this.openedChatList[url].chat.sendFollowResponse(
        requester, response, requester_tabid);
  }
}

Metaweb.Model.Chat = function(chatController, controller, room_id) {
  this.chatController = chatController;
  this.controller = controller;
  this.account = this.controller.model.connection.xmppConnection.account;
  this.username = this.controller.model.connection.xmppConnection.username;
  this.room_id = room_id;
  
  var service = this.controller.model.prefManager.getCharPref("xmppservice");
  var domain = this.controller.model.prefManager.getCharPref("xmppdomain");
  this.room_jid = room_id + '@' + service + '.' + domain;
  this.channel = XMPP.createChannel();
  this.roomParticipant = new Object();
  this.chatContent = new Array();
};

Metaweb.Model.Chat.prototype = {
  joinRoom : function() {
    this.addPresenceListener();
    this.addMessageListener();
    var to_string = this.room_jid + '/' + this.username;
    XMPP.send(this.account.jid,
        <presence from={this.account.jid}
                  to={to_string}/>);
  },

  leaveRoom : function() {
    if (this.room_jid == null)
      return;
    var to_string = this.room_jid + '/' + this.username;
    XMPP.send(this.account.jid,
        <presence from={this.account.jid}
                  to={to_string}
                  type='unavailable'/>);
    this.channel.forget(this.presenceListener);
    this.channel.forget(this.messageListener);
    this.channel.forget(this.privateMessageListener);
    this.controller.onLeaveChatRoom();
    this.roomParticipant = new Object();
  },

  addPresenceListener : function() {
    var temp = this;
    var presence_handler = function (presence) {
      temp.onPresenceReceived.call(temp, presence);
    };
    
    this.presenceListener = this.channel.on({
      event : 'presence',
      direction : 'in',
      stanza : function(s) {
        return s.@from.indexOf(temp.room_jid + '/') == 0;
      }},
      presence_handler);
  },

  addMessageListener : function() {
    var temp = this;
    var message_handler = function (message) {
      temp.onMessageReceived.call(temp, message);
    };

    this.messageListener = this.channel.on({
      event : 'message',
      direction : 'in',
      stanza : function(m) {
        return m.@type == "groupchat" && 
               m.@from.indexOf(temp.room_jid + '/') == 0;
      }},
      message_handler);

    var private_message_handler = function(message) {
      if (message.stanza.follow) {
        if (message.stanza.follow.@type == "request") {
          temp.onFollowRequestReceived.call(temp, message);
        } else {
          //alert('private_message_handler - ELSE branch');
          temp.onFollowResponseReceived.call(temp, message);
        }
      }
    };

    this.privateMessageListener = this.channel.on({
      event : 'message',
      direction : 'in',
      stanza : function(m) {
        return m.@type == "chat" &&
               m.@from.indexOf(temp.room_jid + '/') == 0;
      }},
      private_message_handler);
  },

  onPresenceReceived : function(presence) {
    if (presence.stanza.@type.length() == 0 || 
        presence.stanza.@type == 'available') {
      var jid = String(presence.stanza.@from);
      if (!(jid in this.roomParticipant)) {
        this.roomParticipant[jid] = jid.split('/')[1];
        this.chatController.onUserOnline(this, this.roomParticipant[jid]);
      }
    } else if (presence.stanza.@type == 'unavailable') {
      var jid = presence.stanza.@from;
      if (jid in this.roomParticipant) {
        this.chatController.onUserOffline(this, this.roomParticipant[jid]);
        delete this.roomParticipant[jid];
      }
    }
  },

  onMessageReceived : function(message) {
    var temp = {from : message.stanza.@from.split('/')[1],
                content : message.stanza.body};
    if (this.chatContent.length <= 50) {
      this.chatContent.push(temp);
    } else {
      this.chatContent.splice(0,1,temp); 
    }
    this.chatController.onMessageReceived(
        this, this.chatContent[this.chatContent.length - 1]);
  },

  sendGroupChat : function(message) {
    XMPP.send(this.account.jid,
        <message from={this.account.jid}
                 to={this.room_jid}
                 type='groupchat'>
          <body>{message}</body>
        </message>);
  },

  /* to is groupchat alias
   * tabid is the follower tabid
   */
  sendFollowRequest : function(to, url, tabid) {
    var to_jid = this.room_jid + "/" + to;
    XMPP.send(this.account.jid,
        <message from={this.account.jid}
                 to={to_jid}
                 type="chat">
          <body/>
          <follow type="request"
                  userid={this.account.jid}
                  url={url}
                  tabid={tabid}/>
        </message>);
  },

  sendFollowResponse : function(to, response, tabid) {
    var to_jid = this.room_jid + "/" + to;
    XMPP.send(this.account.jid,
        <message from={this.account.jid}
                 to={to_jid}
                 type="chat">
          <body/>
          <follow type="response"
                  response={response}
                  tabid={tabid}
                  userid={this.account.jid}/>
        </message>);
  
  },

  onFollowRequestReceived : function(message) {
    var requester = message.stanza.follow.@userid;
    var url = message.stanza.follow.@url;
    var tabid = message.stanza.follow.@tabid;
    var requester_nick = message.stanza.@from.split('/')[1];
    this.controller.model.openedTabList.onFollowRequestReceived(
        requester, requester_nick, tabid, url);
  },

  onFollowResponseReceived : function(message) {
    var userid = message.stanza.follow.@userid;
    var tabid = message.stanza.follow.@tabid;
    var response = message.stanza.follow.@response;
    this.controller.model.openedTabList.onFollowResponseReceived(
        userid, tabid, response);
  }
};

Metaweb.Model.Comment = function(controller) {
  this.controller = controller;
};

Metaweb.Model.Comment.prototype = {
  newComment : function(url, content, parent_id) {
    //alert ("new comment");
    var args = new Array();
    args["content"] = content;
    args["url"] = url;
    args["parent_id"] = parent_id;
    this.controller.model.connection.doServerRequest("new_comment", args,
        this.controller.model.connection.createServerRequestCallback(
            this, this.onNewCommentSucceed, [url]));
  },

  onNewCommentSucceed : function(url, serverRequest) {
    if (serverRequest.readyState == 4) {
      if (serverRequest.status == 200) {
        // alert(serverRequest.responseText);
        this.getComments(url, false);
      }
    }
  },

  getComments : function(url, page_changed) {
    var args = new Array();
    args["url"] = url;
    this.controller.model.connection.doServerRequest("get_comments", args,
        this.controller.model.connection.createServerRequestCallback(
            this, this.onCommentsReceived, [url, page_changed]));
  },

  onCommentsReceived : function(url, page_changed, serverRequest) {
    if (serverRequest.readyState == 4) {
      if (serverRequest.status == 200) {
        //alert(serverRequest.responseText);
        var xmlresult = new XML(serverRequest.responseText);
        this.controller.showComments(xmlresult);
        var room_id = xmlresult.chatroomjid;
        if (page_changed) {
          this.controller.model.chatRoomList.joinRoom(url, room_id);
          this.controller.model.chatRoomList.changeDisplayedChatRoom(url);
        }
      }
    }
  },

  rateComment : function(comment_id, up) {
    var args = {comment_id : comment_id, up : up};
    this.controller.model.connection.doServerRequest("rate_comment", args,
        this.controller.model.connection.createServerRequestCallback(
            this, this.onRateCommentSuccess));

  },

  onRateCommentSuccess : function(serverRequest) {
    if (serverRequest.readyState == 4) {
      if (serverRequest.status == 200) {
        var xmlresult = new XML(serverRequest.responseText);
        this.getComments(this.controller.model.currentURL, false);
      }
    }
  }
};

Metaweb.Model.TabsChangeListener = function(controller) {
  this.controller = controller;
  this.load_finished = false;
};

Metaweb.Model.TabsChangeListener.prototype = {
  onProgressChange : function(aBrowser, aWebProgress, aRequest,
                              aCurSelfProgress, aMaxSelfProgress,
                              aCurTotalProgress, aMaxTotalProgress) {
  },

  onStateChange : function(aBrowser, aWebProgress, aRequest, aFlag, aStatus) {
    // If you use myListener for more than one tab/window, use
    // aWebProgress.DOMWindow to obtain the tab/window which triggers the state
    // change
    if (aFlag & Metaweb.STATE_START) {
      // This fires when the load event is initiated
      this.load_finished = false;
    }
    if ((aFlag & Metaweb.STATE_STOP) /*&& (this.load_finished == false)*/) {
      // This fires when the load finishes
      this.controller.onPageLoad(aBrowser.currentURI.spec, 
          aBrowser.contentTitle);

      if (this.load_finished == false) {
        this.load_finished = true;
        this.controller.model.openedTabList.afterPageLoad(aBrowser);
	  }
    }
  },

  onLocationChange : function(aBrowser, aProgress, aRequest, aURI) {
    // This fires when the location bar changes; i.e load event is confirmed
    // or when the user switches tabs. If you use myListener for more than one
    // tab/window,
    // use aProgress.DOMWindow to obtain the tab/window which triggered the
    // changee
    // this.controller.model.processNewURL(aURI);
    this.controller.model.openedTabList.onLocationChange(aBrowser, aURI);
  },

  onStatusChange : function(aBrowser, aWebProgress, 
                            aRequest, aStatus, aMessage) {
  },

  onSecurityChange : function(aBrowser, aWebProgress, aRequest, aState) {
  }
};

Metaweb.Model.Rating = function(controller) {
  this.controller = controller;
};

Metaweb.Model.Rating.prototype = {
  updatePageRating : function(title, rating) {
    var args = { url : this.controller.model.currentURL,
                 url_parameter : this.controller.model.currentURLParameter,
                 username : this.controller.model.connection.userName,
                 title : title,
                 rating : rating};
    this.controller.model.connection.doServerRequest(
        "update_page_rating", args,
        this.controller.model.connection.createServerRequestCallback(
            this, this.updatePageRatingResult));
  }, 
  
  updatePageRatingResult : function(serverRequest) { 
    if (serverRequest.readyState == 4) {
      if (serverRequest.status == 200) {
        var xmlresult = new XML(serverRequest.responseText);
        if (xmlresult.returncode == '1') {
          this.getPageRating();
        } else {
          //this.controller.onError(xmlresult.errormsg);
        }
      } else {
        alert('There was a problem with the request.');
      }
    }
  },

  getPageRating : function() {
    var args = { url : this.controller.model.currentURL,
                 url_parameter : this.controller.model.currentURLParameter,
                 username : this.controller.model.connection.userName};

    this.controller.model.connection.doServerRequest(
        "get_page_rating", args,
        this.controller.model.connection.createServerRequestCallback(
            this, this.onPageRatingReceived));
  },

  onPageRatingReceived : function(serverRequest) {
    if (serverRequest.readyState == 4) {
      if (serverRequest.status == 200) {
        var xmlresult = new XML(serverRequest.responseText);
        this.controller.onUpdatePageRatingSuccess(xmlresult.userrating,
                                                  xmlresult.averagerating);
      }
    }
  }
};

Metaweb.Model.Tag = function(controller) {
  this.controller = controller;
}

Metaweb.Model.Tag.prototype = {
  addTag : function(tag) {
    var args = { url : this.controller.model.currentURL,
                 url_parameter : this.controller.model.currentURLParameter,
                 tag : tag};
    this.controller.model.connection.doServerRequest(
        "add_tag", args,
        this.controller.model.connection.createServerRequestCallback(
            this, this.addTagResult));
  },
           
  addTagResult : function(serverRequest) { 
    if (serverRequest.readyState == 4) {
      if (serverRequest.status == 200) {
        //alert(serverRequest.responseText);
        var xmlresult = new XML(serverRequest.responseText);
        if (xmlresult.returncode == '1') {
          this.getPageTags();
        } else {
          //this.controller.onError(xmlresult.errormsg);
        }
      } else {
        alert('There was a problem with the request.');
      }
    }
  },

  getPageTags : function() {
    var args = { url : this.controller.model.currentURL,
                 url_parameter : this.controller.model.currentURLParameter};

    this.controller.model.connection.doServerRequest(
        "get_page_tags", args,
        this.controller.model.connection.createServerRequestCallback(
            this, this.onPageTagsResultReceived));
  },

  onPageTagsResultReceived : function(serverRequest) {
    if (serverRequest.readyState == 4) {
      if (serverRequest.status == 200) {
        // alert(serverRequest.responseText);
        var xmlresult = new XML(serverRequest.responseText);
        this.controller.showTags(xmlresult);
      }
    }
  },

 getRecommendedPageTags : function() {
    var args = { url : this.controller.model.currentURL,
                 url_parameter : this.controller.model.currentURLParameter};

    this.controller.model.connection.doServerRequest(
        "get_recommended_page_tags", args,
        this.controller.model.connection.createServerRequestCallback(
            this, this.onRecommendedPageTagsResultReceived));
  },

  onRecommendedPageTagsResultReceived : function(serverRequest) {
    if (serverRequest.readyState == 4) {
      if (serverRequest.status == 200) {
        //alert(serverRequest.responseText);
        var xmlresult = new XML(serverRequest.responseText);
        this.controller.showTagsTextbox(xmlresult);
      }
    }
  },
 
 
  getPagesWithTag : function(tag) {
    var args = { tag : tag };
    this.controller.model.connection.doServerRequest(
        "get_pages_with_tag", args, 
        this.controller.model.connection.createServerRequestCallback(
            this, this.onPagesWithTagResultReceived));
  },

  onPagesWithTagResultReceived : function(serverRequest) {
    if (serverRequest.readyState == 4) {
      if (serverRequest.status == 200) {
        var xmlresult = new XML(serverRequest.responseText);
        this.controller.showPagesWithTag(xmlresult);
      }
    }
  },

  // up == true -> +1 else -1
  rateTag : function(tag, up) {
    var args = { url : this.controller.model.currentURL,
                 url_parameter : this.controller.model.currentURLParameter,
                 tag : tag, up : up};

    this.controller.model.connection.doServerRequest(
        "rate_tag", args,
        this.controller.model.connection.createServerRequestCallback(
            this, this.addTagResult));
        // change the callback if we need to do something different
  },

  deleteTag : function(tag) {
    var args = { url : this.controller.model.currentURL,
                 url_parameter : this.controller.model.currentURLParameter,
                 tag : tag};

    this.controller.model.connection.doServerRequest(
        "delete_tag", args,
        this.controller.model.connection.createServerRequestCallback(
            this, this.addTagResult));
        // change the callback if we need to do something different
  }
}

Metaweb.Model.ContactList = function(controller) {
  this.controller = controller;
  this.followedList = new Array();
}

Metaweb.Model.ContactList.prototype = {
  followContact : function(username) {
    var args = { username : username};

    this.controller.model.connection.doServerRequest(
        "follow_contact", args,
        this.controller.model.connection.createServerRequestCallback(
            this, this.followUnfollowResult));
        // change the callback if we need to do something different
  },

  unfollowContact : function(username) {
    var args = { username : username};

    this.controller.model.connection.doServerRequest(
        "unfollow_contact", args,
        this.controller.model.connection.createServerRequestCallback(
            this, this.followUnfollowResult));
  },

  followUnfollowResult : function(serverRequest) {
    if (serverRequest.readyState == 4) {
      if (serverRequest.status == 200) {
        //alert(serverRequest.responseText);
        var xmlresult = new XML(serverRequest.responseText);
        this.getContactList();
        this.controller.model.reloadComments();
        this.controller.model.link.getLinks();
      }
    }
  },

  getContactList : function() {
    var args = new Array();

    this.controller.model.connection.doServerRequest(
        "get_contact_list", args, 
        this.controller.model.connection.createServerRequestCallback(
            this, this.onGetContactListResultReceived));
  },

  onGetContactListResultReceived : function(serverRequest) {
    if (serverRequest.readyState == 4) {
      if (serverRequest.status == 200) {
        //alert(serverRequest.responseText);
        var xmlresult = new XML(serverRequest.responseText);
        this.followedList = new Array();
        for (var i = 0; i < xmlresult.followed.user.length(); ++i) {
          this.followedList.push(xmlresult.followed.user[i]);
        }
        this.controller.showContactList(xmlresult);
      }
    }
  },

  isFollowing : function(user) {
    for (var i = 0; i < this.followedList.length; ++i) {
      if (String(user) == String(this.followedList[i]))
        return true;
    }
  }
}

Metaweb.Model.Link = function(controller) {
  this.controller = controller;
}

Metaweb.Model.Link.prototype = {
  addLink : function(from_url, to_url, note) {
    var args = {from_url : Metaweb.Utility.getBareURL(from_url),
                from_url_param : Metaweb.Utility.getURLParameter(from_url),
                to_url : Metaweb.Utility.getBareURL(to_url),
                to_url_param : Metaweb.Utility.getURLParameter(to_url),
                note : note};

    this.controller.model.connection.doServerRequest(
        "add_link", args,
        this.controller.model.connection.createServerRequestCallback(
            this, this.addLinkResult));
        // change the callback if we need to do something different
  },
  
  addLinkResult : function(serverRequest) {
    if (serverRequest.readyState == 4) {
      if (serverRequest.status == 200) {
        //alert(serverRequest.responseText);
        var xmlresult = new XML(serverRequest.responseText);
        this.getLinks();
      }
    }
  },

  deleteLink : function (from_url, from_url_param, to_url, to_url_param) {
    var args = {from_url : from_url,
                from_url_param : from_url_param,
                to_url : to_url,
                to_url_param : to_url_param};

    this.controller.model.connection.doServerRequest(
        "delete_link", args,
        this.controller.model.connection.createServerRequestCallback(
            this, this.addLinkResult));
        // change the callback if we need to do something different

  },

  getLinks : function() {
    var args = {url : this.controller.model.currentURL,
                url_param : this.controller.model.currentURLParameter};
    this.controller.model.connection.doServerRequest(
        "get_links", args,
        this.controller.model.connection.createServerRequestCallback(
            this, this.getLinksResult));
        // change the callback if we need to do something different
  },

  getLinksResult : function(serverRequest) {
    if (serverRequest.readyState == 4) {
      if (serverRequest.status == 200) {
        // alert(serverRequest.responseText);
        var xmlresult = new XML(serverRequest.responseText);
        this.controller.showLinks(xmlresult);
      }
    }
  },

  rateLinkTo : function(from_url, up) {
    var from_url_param = Metaweb.Utility.getURLParameter(from_url);
    from_url = Metaweb.Utility.getBareURL(from_url);
    var to_url = this.controller.model.currentURL;
    var to_url_param = this.controller.model.currentURLParameter;

    this.rateLink(from_url, from_url_param, to_url, to_url_param, up);
  },

  rateLinkFrom : function(to_url, up) {
    var to_url_param = Metaweb.Utility.getURLParameter(to_url);
    to_url = Metaweb.Utility.getBareURL(to_url);
    var from_url = this.controller.model.currentURL;
    var from_url_param = this.controller.model.currentURLParameter;

    this.rateLink(from_url, from_url_param, to_url, to_url_param, up);
  },

  rateLink : function(from_url, from_url_param, to_url, 
                      to_url_param, up) {
    var args = { from_url : from_url,
                 from_url_param : from_url_param,
                 to_url : to_url,
                 to_url_param : to_url_param,
                 up : up };
    this.controller.model.connection.doServerRequest(
        "rate_link", args,
        this.controller.model.connection.createServerRequestCallback(
            this, this.rateLinkResult));
  },

  rateLinkResult : function(serverRequest) {
    if (serverRequest.readyState == 4) {
      if (serverRequest.status == 200) {
        // alert(serverRequest.responseText);
        var xmlresult = new XML(serverRequest.responseText);
        this.getLinks();
      }
    }
  },

  getToLinkComment : function(from_url, from_url_param) {
    var to_url = this.controller.model.currentURL;
    var to_url_param = this.controller.model.currentURLParameter;

    this.getLinkComment(from_url, from_url_param, to_url, to_url_param, true);
  },

  getFromLinkComment : function(to_url, to_url_param) {
    var from_url = this.controller.model.currentURL;
    var from_url_param = this.controller.model.currentURLParameter;

    this.getLinkComment(from_url, from_url_param, to_url, to_url_param, false);
  },

  getLinkComment : function(from_url, from_url_param, to_url, to_url_param, to) {
    var args = { from_url : from_url,
                 from_url_param : from_url_param,
                 to_url : to_url,
                 to_url_param : to_url_param };
    var param = null;
    if (to) {
      param = [from_url, from_url_param, to];
    } else {
      param = [to_url, to_url_param, to];
    }
    this.controller.model.connection.doServerRequest(
        "get_link_comment", args,
        this.controller.model.connection.createServerRequestCallback(
            this, this.getLinkCommentResult, param));
  },

  getLinkCommentResult : function(url, url_param, to, serverRequest) {
    if (serverRequest.readyState == 4) {
      if (serverRequest.status == 200) {
        // alert(serverRequest.responseText);
        var xmlresult = new XML(serverRequest.responseText);
        this.controller.showLinkComment(xmlresult, url, url_param, to);
      }
    }
  }
}



Metaweb.Model.DocumentHandler = function(controller) 
{
	this.controller = controller;
}

Metaweb.Model.DocumentHandler.prototype = 
{
    //////////////////////////////////////
    //
    // Insert ratings into the current web page: 
    // each rating of a link is requested by http request
    //
    insertPageRatings: function(doInsert) {
      if (doInsert){
	    var len = content.document.getElementsByTagName("a").length;
	    //var domain = content.window.location.hostname;	// currently not needed
	    for(var i = 0; i < len; i++) {
          var linkDOMItem = content.document.getElementsByTagName("a").item(i);
          if(linkDOMItem.hasAttribute('href')) {
            if (linkDOMItem.href.indexOf("http://") > -1) {
		      var args = { url : Metaweb.Utility.getBareURL(linkDOMItem.href),
                           url_parameter : Metaweb.Utility.getURLParameter(linkDOMItem.href),
                           username : this.controller.model.connection.userName};

              this.controller.model.connection.doServerRequest(
                  "get_page_rating", args,
                  this.controller.model.connection.createServerRequestCallback(
                  this, this.insertSinglePageRating, [linkDOMItem]));
            }
          }
        }
      }
      else {
        content.window.location.reload();
      }
    },

    insertSinglePageRating : function(linkDOMItem, serverRequest) {
      if (serverRequest.readyState == 4) {
        if (serverRequest.status == 200) {
          var xmlresult = new XML(serverRequest.responseText);
          if (xmlresult.averagerating != "") { 
            linkDOMItem.innerHTML += " " + this.convertRatingToUnicodeBar(xmlresult.userrating) 
                + "" + this.convertRatingToUnicodeBar(xmlresult.averagerating) + " ";
		      } else {
		        linkDOMItem.innerHTML += "";
          }
        }
      }
    },

    
    //////////////////////////////////////
    //
    // Insert ratings into the current web page: 
    // all ratings are request by one single http request
    //
    insertPageRatingsBulk: function(doInsert) {
      if (doInsert){
        var len = content.document.getElementsByTagName("a").length;
        //var domain = content.window.location.hostname;	// currently not needed
        var urlString = "";
        var paramsString = "";

        var linkDOMItems = new Array();
        var cnt = 0; var pos = 0;
	    for(var i = 0; i < len; i++) {
        var linkDOMItem = content.document.getElementsByTagName("a").item(i);
        if(linkDOMItem.hasAttribute('href')) {
          if (linkDOMItem.href.indexOf("http://") > -1) {
            linkDOMItems[cnt] = linkDOMItem
            urlString += Metaweb.Utility.getBareURL(linkDOMItem.href) + "|||";        
            paramsString += Metaweb.Utility.getURLParameter(linkDOMItem.href) + "|||";        
            cnt++;
          }
        }
      }
      urlString = urlString.substring(0, urlString.length - 3); 
      paramsString = paramsString.substring(0, paramsString.length - 3);
      var args = { url_string : urlString,
                   params_string : paramsString,
                   username : this.controller.model.connection.userName};

      this.controller.model.connection.doServerRequest(
          "get_page_rating_bulk", args,
          this.controller.model.connection.createServerRequestCallback(
          this, this.insertPageRatingBulk, [linkDOMItems]));
      }
      else {
        content.window.location.reload();
      }
    },
    
    insertPageRatingBulk : function(linkDOMItems, serverRequest) {
      if (serverRequest.readyState == 4) {
        if (serverRequest.status == 200) {
          result = serverRequest.responseXML;
          var currentLinkDOMItem = null; 
          var currentUserRating = 0; 
          var currentAverageRating = 0; 
          var currentLink = null; 
          currentLinkNum = 0;

          for (var i = 0; i < result.getElementsByTagName('link').length; i++) {
            currentLink  = result.getElementsByTagName('link')[i];
            currentLinkNum = parseInt(currentLink.getAttribute("num"));
            currentLinkDOMItem = linkDOMItems[currentLinkNum];
            for (var j = 0; j < currentLink.childNodes.length; j++) {
              if (currentLink.childNodes[j].nodeName == "userrating")
                currentUserRating = parseFloat(currentLink.childNodes[j].firstChild.nodeValue);
              else if (currentLink.childNodes[j].nodeName == "averagerating")
                currentAverageRating = parseFloat(currentLink.childNodes[j].firstChild.nodeValue);
            }
            var oldRating = currentLinkDOMItem.getElementsByClassName('metaweb-rating');
            if (oldRating.length > 0) {
              currentLinkDOMItem.removeChild(oldRating[0]);
            }
            currentLinkDOMItem.innerHTML += "<span class='metaweb-rating'> " 
                + this.convertRatingToUnicodeBar(currentUserRating) + "" 
                + this.convertRatingToUnicodeBar(currentAverageRating) 
                + " </span>";
          }
        }
      }
    },

    //convertRatingToUnicodeBar : function(rating) {
    //    value = parseInt(rating);
        //dump(value);
        //if (value > 4.5)
            //return "\u2589";
        //else if ((value > 3.5) && (value <= 4.5))
            //return "\u2587";
        //else if ((value > 2.5) && (value <= 3.5))
            //return "\u2585";
        //else if ((value > 1.5) && (value <= 2.5))
            //return "\u2583";
        //else if ((value > 0.5) && (value <= 1.5))
            //return "\u2582";
        //else
            //return "  ";
    //},

    convertRatingToUnicodeBar : function(rating) {
        value = parseFloat(rating);
        if (value > 4)
            return "\u25CF";
        else if ((value > 3) && (value <= 4))
            return "\u25D5";
        else if ((value > 2) && (value <= 3))
            return "\u25D1";
        else if ((value > 1) && (value <= 2))
            return "\u25D4";
        else if ((value > 0) && (value <= 1))
            return "\u25CB";
        else
            return "";
    },

    afterPageLoad : function() {
      var tabSettingInsertRatingsValue = this.controller.view.toolbar.settings.isInsertRatingsChecked();
      if (tabSettingInsertRatingsValue) {
        //this.insertPageRatings(tabSettingInsertRatingsValue);
        this.insertPageRatingsBulk(tabSettingInsertRatingsValue);
      }
    },
}


Metaweb.Model.ContextMenuHandler = function(controller) 
{
	this.controller = controller;
}

Metaweb.Model.ContextMenuHandler.prototype = 
{
    init : function() {
		document.getElementById("contentAreaContextMenu").addEventListener("popupshowing", this.draw, false);
	},   
    
    handleLocation : function() {
		var selectedText = this.getSelectedText();
        alert (selectedText);
	},   
    
    draw: function() {
		var elem = document.getElementById("handleLocation");
		if (gContextMenu.isTextSelected) {
			elem.setAttribute("disabled", false);
		} else {
			elem.setAttribute("disabled", true);
        }
	},
    
    getSelectedText : function()
	{
		var focusedWindow = document.commandDispatcher.focusedWindow;
		var selectedText = focusedWindow.getSelection();
		return selectedText.toString();
	}

};


Metaweb.Model.AutoCompleter = function(controller) 
{
	this.controller = controller;
};

Metaweb.Model.AutoCompleter.prototype = 
{
    init : function() {
        try {
            var comp = Components.classes['@mozilla.org/autocomplete/search;1?name=simple-autocomplete'].createInstance();
            comp = comp.QueryInterface(Components.interfaces.nsIAutoCompleteSearch);
            comp.stopSearch();
        } catch (ex) {
            dump(ex + "\n");
        }
	}  
    
}
    

Application.storage.set("Metaweb", Metaweb);
Application.storage.set("jQuery", jQuery);
